/*
 ------------------------------------------------------------------------------
 Copyright (C) 2013 Eternal Games.

 This file is part of the GLQuake source code.

 The GLQuake source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The GLQuake source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the GLQuake source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


//
// trajectory.h - ...
//


#ifndef __GAME_TRAJECTORY_H__
#define __GAME_TRAJECTORY_H__


enum trType_t {
	TR_STATIONARY,
	TR_INTERPOLATE,
	TR_LINEAR,
	TR_LINEAR_STOP,
	TR_GRAVITY,
	TR_GRAVITY_PAUSED,
	TR_SINE,
	TR_ACCELERATE,
	TR_DECELERATE
};

template <class T> class etTrajectory {
public:
	int						type;

	int						time;
	int						duration;

	T						base;
	T						delta;

public:
							etTrajectory ();

	void					Init (const int type, const int time, const int duration, const T &base, const T &delta);

	bool					IsDone (int atTime) const;

	T						Evaluate (int atTime, const float gravity = DEFAULT_GRAVITY) const;
	T						EvaluateDelta (int atTime, const float gravity = DEFAULT_GRAVITY) const;
};

template <class T> INLINE etTrajectory<T>::etTrajectory (){

}

template <class T> INLINE void etTrajectory<T>::Init (const int type, const int time, const int duration, const T &base, const T &delta){

	this->type = type;

	this->time = time;
	this->duration = duration;

	this->base[0] = base[0];
	this->base[1] = base[1];
	this->base[2] = base[2];

	this->delta[0] = delta[0];
	this->delta[1] = delta[1];
	this->delta[2] = delta[2];
}

template <class T> INLINE bool etTrajectory<T>::IsDone (int atTime) const {

	if (type != TR_LINEAR_STOP && type != TR_ACCELERATE && type != TR_DECELERATE)
		return false;

	if (atTime < time + duration)
		return false;

	return true;
}

template <class T> INLINE T etTrajectory<T>::Evaluate (int atTime, const float gravity) const {

	T		result;
	float	deltaTime;
	float	phase;

	if (atTime <= time){
		result[0] = base[0];
		result[1] = base[1];
		result[2] = base[2];

		return result;
	}

	switch (type){
	case TR_STATIONARY:
		result[0] = base[0];
		result[1] = base[1];
		result[2] = base[2];

		break;
	case TR_INTERPOLATE:
		result[0] = base[0];
		result[1] = base[1];
		result[2] = base[2];

		break;
	case TR_LINEAR:
		deltaTime = MS2SEC(atTime - time);

		result[0] = base[0] + delta[0] * deltaTime;
		result[1] = base[1] + delta[1] * deltaTime;
		result[2] = base[2] + delta[2] * deltaTime;

		break;
	case TR_LINEAR_STOP:
		if (atTime > time + duration)
			atTime = time + duration;

		deltaTime = MS2SEC(atTime - time);

		result[0] = base[0] + delta[0] * deltaTime;
		result[1] = base[1] + delta[1] * deltaTime;
		result[2] = base[2] + delta[2] * deltaTime;

		break;
	case TR_GRAVITY:
		deltaTime = MS2SEC(atTime - time);

		result[0] = base[0] + delta[0] * deltaTime;
		result[1] = base[1] + delta[1] * deltaTime;
		result[2] = base[2] + delta[2] * deltaTime - gravity * 0.5f * Square(deltaTime);

		break;
	case TR_GRAVITY_PAUSED:
		result[0] = base[0];
		result[1] = base[1];
		result[2] = base[2];

		break;
	case TR_SINE:
		deltaTime = (atTime - time) / (float)duration;

		phase = glqMath::Sin(deltaTime * M_PI_TWO);

		result[0] = base[0] + delta[0] * phase;
		result[1] = base[1] + delta[1] * phase;
		result[2] = base[2] + delta[2] * phase;

		break;
	case TR_ACCELERATE:
		if (atTime > time + duration)
			atTime = time + duration;

		deltaTime = MS2SEC(atTime - time);

		phase = 0.5f * Square(deltaTime) / MS2SEC(duration);

		result[0] = base[0] + delta[0] * phase;
		result[1] = base[1] + delta[1] * phase;
		result[2] = base[2] + delta[2] * phase;

		break;
	case TR_DECELERATE:
		if (atTime > time + duration)
			atTime = time + duration;

		deltaTime = MS2SEC(atTime - time);

		phase = 0.5f * Square(deltaTime) / MS2SEC(duration);

		result[0] = base[0] + delta[0] * deltaTime - delta[0] * phase;
		result[1] = base[1] + delta[1] * deltaTime - delta[1] * phase;
		result[2] = base[2] + delta[2] * deltaTime - delta[2] * phase;

		break;
	default:
		G_Error("etTrajectory::Evaluate: bad type (%i)", type);
	}

	return result;
}

template <class T> INLINE T etTrajectory<T>::EvaluateDelta (int atTime, const float gravity) const {

	T		result;
	float	deltaTime;
	float	phase;

	if (atTime <= time){
		result[0] = 0.0f;
		result[1] = 0.0f;
		result[2] = 0.0f;

		return result;
	}

	switch (type){
	case TR_STATIONARY:
		result[0] = 0.0f;
		result[1] = 0.0f;
		result[2] = 0.0f;

		break;
	case TR_INTERPOLATE:
		result[0] = 0.0f;
		result[1] = 0.0f;
		result[2] = 0.0f;

		break;
	case TR_LINEAR:
		result[0] = delta[0];
		result[1] = delta[1];
		result[2] = delta[2];

		break;
	case TR_LINEAR_STOP:
		if (atTime > time + duration){
			result[0] = 0.0f;
			result[1] = 0.0f;
			result[2] = 0.0f;
		}
		else {
			result[0] = delta[0];
			result[1] = delta[1];
			result[2] = delta[2];
		}

		break;
	case TR_GRAVITY:
		deltaTime = MS2SEC(atTime - time);

		result[0] = delta[0];
		result[1] = delta[1];
		result[2] = delta[2] - gravity * deltaTime;

		break;
	case TR_GRAVITY_PAUSED:
		result[0] = 0.0f;
		result[1] = 0.0f;
		result[2] = 0.0f;

		break;
	case TR_SINE:
		deltaTime = (atTime - time) / (float)duration;

		phase = 0.5f * glqMath::Cos(deltaTime * M_PI_TWO);

		result[0] = delta[0] * phase;
		result[1] = delta[1] * phase;
		result[2] = delta[2] * phase;

		break;
	case TR_ACCELERATE:
		if (atTime > time + duration){
			result[0] = 0.0f;
			result[1] = 0.0f;
			result[2] = 0.0f;
		}
		else {
			deltaTime = MS2SEC(atTime - time);

			result[0] = delta[0] * deltaTime * deltaTime;
			result[1] = delta[1] * deltaTime * deltaTime;
			result[2] = delta[2] * deltaTime * deltaTime;
		}

		break;
	case TR_DECELERATE:
		if (atTime > time + duration){
			result[0] = 0.0f;
			result[1] = 0.0f;
			result[2] = 0.0f;
		}
		else {
			deltaTime = MS2SEC(atTime - time);

			result[0] = delta[0] * deltaTime;
			result[1] = delta[1] * deltaTime;
			result[2] = delta[2] * deltaTime;
		}

		break;
	default:
		G_Error("etTrajectory::EvaluateDelta: bad type (%i)", type);
	}

	return result;
}


#endif	// __GAME_TRAJECTORY_H__